/*
Copyright 2024 MySQL Parser Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

import (
	"fmt"
	"strconv"
	"strings"
)

// BindVar represents a bind variable in SQL
type BindVar struct {
	Key   string
	Value interface{}
}

// BindVariables is a map of bind variables
type BindVariables map[string]*BindVar

// Argument represents a bind variable placeholder
type Argument struct {
	Name string
}

// iExpr implements Expr
func (node *Argument) iExpr() {}

// Format formats the argument
func (node *Argument) Format(buf *TrackedBuffer) {
	if node.Name != "" {
		buf.WriteString(":")
		buf.WriteString(node.Name)
	} else {
		buf.WriteString("?")
	}
}

// String returns the string representation
func (node *Argument) String() string {
	return String(node)
}

// ListArg represents a list argument for IN clauses
type ListArg struct {
	Name string
}

// iExpr implements Expr
func (node *ListArg) iExpr() {}

// Format formats the list argument
func (node *ListArg) Format(buf *TrackedBuffer) {
	buf.WriteString("::")
	buf.WriteString(node.Name)
}

// String returns the string representation
func (node *ListArg) String() string {
	return String(node)
}

// NewArgument creates a new Argument
func NewArgument(name string) *Argument {
	return &Argument{Name: name}
}

// NewListArg creates a new ListArg
func NewListArg(name string) *ListArg {
	return &ListArg{Name: name}
}

// ParseAndBind parses a SQL string and binds variables
func ParseAndBind(sql string, bindVars BindVariables) (string, error) {
	stmt, err := Parse(sql)
	if err != nil {
		return "", err
	}

	// Create a new buffer for the bound query
	buf := NewTrackedBuffer(nil)
	
	// Apply bind variables
	boundStmt, err := bindStatement(stmt, bindVars)
	if err != nil {
		return "", err
	}
	
	boundStmt.Format(buf)
	return buf.String(), nil
}

// bindStatement applies bind variables to a statement
func bindStatement(stmt Statement, bindVars BindVariables) (Statement, error) {
	cloned := Clone(stmt)
	if clonedStmt, ok := cloned.(Statement); ok {
		err := Walk(func(node SQLNode) (bool, error) {
			return bindNode(node, bindVars)
		}, clonedStmt)
		if err != nil {
			return nil, err
		}
		return clonedStmt, nil
	}
	return stmt, nil
}

// bindNode applies bind variables to a single node
func bindNode(node SQLNode, bindVars BindVariables) (bool, error) {
	switch n := node.(type) {
	case *Argument:
		// Replace argument with bound value
		if bindVar, exists := bindVars[n.Name]; exists {
			// Convert the bind variable to a literal
			literal, err := bindVarToLiteral(bindVar)
			if err != nil {
				return false, err
			}
			// Replace the argument with the literal
			// Note: This is a simplified approach. In a real implementation,
			// you would need to modify the parent node to replace the child.
			_ = literal
		}
	case *ListArg:
		// Handle list arguments for IN clauses
		if bindVar, exists := bindVars[n.Name]; exists {
			// Convert the bind variable to a list of literals
			_, err := bindVarToLiteralList(bindVar)
			if err != nil {
				return false, err
			}
			// Replace the list argument with the literals
			// Note: This is a simplified approach.
		}
	}
	return true, nil
}

// bindVarToLiteral converts a bind variable to a literal
func bindVarToLiteral(bindVar *BindVar) (*Literal, error) {
	switch v := bindVar.Value.(type) {
	case string:
		return &Literal{Type: StrVal, Val: "'" + strings.ReplaceAll(v, "'", "''") + "'"}, nil
	case int:
		return &Literal{Type: IntVal, Val: strconv.Itoa(v)}, nil
	case int64:
		return &Literal{Type: IntVal, Val: strconv.FormatInt(v, 10)}, nil
	case float64:
		return &Literal{Type: FloatVal, Val: strconv.FormatFloat(v, 'f', -1, 64)}, nil
	case bool:
		if v {
			return &Literal{Type: IntVal, Val: "1"}, nil
		}
		return &Literal{Type: IntVal, Val: "0"}, nil
	case nil:
		return nil, fmt.Errorf("cannot bind nil value")
	default:
		return &Literal{Type: StrVal, Val: "'" + fmt.Sprintf("%v", v) + "'"}, nil
	}
}

// bindVarToLiteralList converts a bind variable to a list of literals
func bindVarToLiteralList(bindVar *BindVar) ([]*Literal, error) {
	switch v := bindVar.Value.(type) {
	case []interface{}:
		literals := make([]*Literal, len(v))
		for i, item := range v {
			literal, err := bindVarToLiteral(&BindVar{Value: item})
			if err != nil {
				return nil, err
			}
			literals[i] = literal
		}
		return literals, nil
	case []string:
		literals := make([]*Literal, len(v))
		for i, item := range v {
			literals[i] = &Literal{Type: StrVal, Val: "'" + strings.ReplaceAll(item, "'", "''") + "'"}
		}
		return literals, nil
	case []int:
		literals := make([]*Literal, len(v))
		for i, item := range v {
			literals[i] = &Literal{Type: IntVal, Val: strconv.Itoa(item)}
		}
		return literals, nil
	default:
		return nil, fmt.Errorf("unsupported list type: %T", v)
	}
}

// ExtractBindVars extracts bind variables from a statement
func ExtractBindVars(stmt Statement) []string {
	var bindVars []string
	Walk(func(node SQLNode) (bool, error) {
		switch n := node.(type) {
		case *Argument:
			if n.Name != "" {
				bindVars = append(bindVars, n.Name)
			}
		case *ListArg:
			if n.Name != "" {
				bindVars = append(bindVars, n.Name)
			}
		}
		return true, nil
	}, stmt)
	return bindVars
}

// NormalizeStatement normalizes a statement by replacing literals with bind variables
func NormalizeStatement(stmt Statement) (Statement, BindVariables, error) {
	bindVars := make(BindVariables)
	counter := 0
	
	cloned := Clone(stmt)
	if clonedStmt, ok := cloned.(Statement); ok {
		err := Walk(func(node SQLNode) (bool, error) {
			switch n := node.(type) {
			case *Literal:
				// Replace literal with bind variable
				counter++
				bindVarName := fmt.Sprintf("v%d", counter)
				
				// Convert literal value
				var value interface{}
				switch n.Type {
				case StrVal:
					// Remove quotes
					val := n.Val
					if len(val) >= 2 && val[0] == '\'' && val[len(val)-1] == '\'' {
						value = val[1 : len(val)-1]
					} else {
						value = val
					}
				case IntVal:
					if v, err := strconv.ParseInt(n.Val, 10, 64); err == nil {
						value = v
					} else {
						value = n.Val
					}
				case FloatVal:
					if v, err := strconv.ParseFloat(n.Val, 64); err == nil {
						value = v
					} else {
						value = n.Val
					}
				default:
					value = n.Val
				}
				
				bindVars[bindVarName] = &BindVar{Key: bindVarName, Value: value}
				
				// Note: In a real implementation, you would replace the literal
				// with an Argument node in the parent.
			}
			return true, nil
		}, clonedStmt)
		if err != nil {
			return nil, nil, err
		}
		return clonedStmt, bindVars, nil
	}
	return stmt, bindVars, nil
}